use std::process::Command;
use std::collections::HashMap;
use tokio::time::Instant;
use log::{debug, trace};
use crate::task_confs::*;

extern crate alloc;

#[derive(Debug, Clone)]
pub struct Task {
    pub name: String,
    pub required: Vec<String>,
    pub cmds: Vec<String>,
    pub indegree: usize,
    pub visited: bool
}

impl Task {
    pub fn new(tc: TaskConfig) -> Self {
        let mut t = Task {
            name: tc.name.unwrap(),
            required: tc.required.unwrap_or(Vec::new()),
            cmds: tc.cmds.unwrap_or(Vec::new()),
            indegree: 0,
            visited: false
        };
        t.indegree = t.required.len();
        t
    }

    pub fn is_ready(self: &Self) -> bool {
        self.visited == false && self.indegree == 0
    }

    pub fn decrease(self: &mut Self) {
        if self.indegree > 0 {
            self.indegree -= 1;
        }
    }

    pub fn get_clone(self: &mut Self) -> Self {
        self.visited = true;
        self.clone()
    }

    pub fn exec_commands(self: &Self) -> String {
        let start = Instant::now();
        for cmd in self.cmds.iter() {
            let c: Vec<&str> = cmd.trim().split(" ").collect();
            let output = Command::new(&c[0]).args(&c[1..]).output();
            match output {
                Ok(v) => {
                    trace!("Task({:?}) executed command output : {:?}", self.name, v);
                }
                Err(e) => {
                    trace!("Task({:?}) error : {:?}", self.name, e);
                }
            }
        }
        let duration = Instant::now().duration_since(start);
        debug!("Task({:?}) consumed : {:?}", self.name, duration);
        self.name.clone()
    }
}

pub struct TaskStat {
    pub taskmap: HashMap<String, Task>,
    pub dependances: HashMap<String, Vec<String>>
}

impl TaskStat {
    pub fn new() -> Self {
        TaskStat {
            taskmap: HashMap::<String, Task>::new(),
            dependances: HashMap::<String, Vec<String>>::new()
        }
    }

    pub fn init_stat_from_taskconfs(self: &mut Self, tc: TaskConfs) {
        debug!("Total number of tasks : {}", tc.len());
        for item in tc.task_configs.into_iter() {
            let key = String::from(item.name.as_ref().unwrap());
            let task = Task::new(item);
            
            for name in task.required.iter() {
                self.dependances.entry(name.to_string()).or_insert(Vec::new()).push(key.clone());
            }

            self.taskmap.entry(key.clone()).or_insert(task);
        }
    }
}

#[cfg(test)]
mod tests {
    use std::path::PathBuf;
    use super::*;

    #[test]
    fn test_init_stat_from_taskconfs() {
        let mut tc = TaskConfs::new();
        tc.read_confs_from_dir(PathBuf::from("./configs"));
        let mut ts = TaskStat::new();
        ts.init_stat_from_taskconfs(tc);
        assert_eq!(3, ts.dependances.len());
        assert_eq!(4, ts.taskmap.len());
    }

    #[test]
    fn test_exec_commands() {
        let tc = TaskConfig {
            name: Some(String::from("sleep")),
            required: None,
            cmds: Some(vec![String::from("sleep 1")])
        };
        let task = Task::new(tc);
        assert_eq!(String::from("sleep"), task.exec_commands());
    }

    #[test]
    fn test_is_ready() {
        let tc = TaskConfig {
            name: Some(String::from("sleep")),
            required: None,
            cmds: Some(vec![String::from("sleep 1")])
        };
        let mut task = Task::new(tc);
        assert_eq!(true, task.is_ready());
        let tmp = task.get_clone();
        assert_eq!(false, tmp.is_ready());
    }
}